package com.istock.union.user.filter;

import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpStatus;

import com.istock.base.serializer.JsonSerializer;
import com.istock.base.serializer.json.JsonSerializerFactory;
import com.istock.union.user.client.SSOClientUtils;
import com.istock.union.user.model.SSOUser;
import com.istock.union.user.service.TokenService;
import com.istock.union.user.utils.WebCommonUtils;

/**
 * sso的filter,用于识别请求里面的登录标识
 * 如果没有登录标识,则跳转到sso登录页页面
 * 
 * @author senvon
 *
 */
public class SSOFilter implements Filter {
	/**
	 * 忽略的请求正则表达式
	 */
	private String ignorRegx = "";
	
	/**
	 * 登录标识的存储key
	 * 不管登录标识存储在什么地方,都需要一个key来作为获取的标识
	 * 这个就是获取的key
	 */
	private String tokenName = "";
	
	/**
	 * 登录页面所在的url
	 */
	@Value("${sso.url.login}")
	private String loginUrl;
	
	@Autowired
	private TokenService tokenService;
	
	/**
	 *  当SSOFilter验证token成功以后,得到SSOUser的回调
	 *  用于SSO的客户端应用在SSO登录以后的回调
	 */
	private LoginCallback callback;
	
	/**
	 * 该字段用于生成loginUrl后面的跳转url
	 * 用于login以后的跳转
	 */
	private String afterLoginUrl;
	
	public void setAfterLoginUrl(String afterLoginUrl) {
		this.afterLoginUrl = afterLoginUrl;
	}

	public void setCallback(LoginCallback callback) {
		this.callback = callback;
	}

	private Logger logger = LoggerFactory.getLogger(getClass());
	
	public String getIgnorRegx() {
		return ignorRegx;
	}

	public void setIgnorRegx(String ignorRegx) {
		this.ignorRegx = ignorRegx;
	}

	public String getTokenName() {
		return tokenName;
	}

	public void setTokenName(String tokenName) {
		this.tokenName = tokenName;
	}

	public String getLoginUrl() {
		return loginUrl;
	}

	public void setLoginUrl(String loginUrl) {
		this.loginUrl = loginUrl;
	}

	public TokenService getTokenService() {
		return tokenService;
	}

	public void setTokenService(TokenService tokenService) {
		this.tokenService = tokenService;
	}

	@Override
	public void init(FilterConfig filterConfig) throws ServletException {
		
	}

	/** 
	 * 过滤判断 , 判断步骤如下
	 * 1.对正则表达式判断,是否跳过当前的登录标识判断
	 * 2.获取当前的登录标识,如果能获取登录标识,则对登录标识进行判断,识别登录标识,获取登录信息
	 * 3.如果没有登录标识,则生成fromurl,跳转到sso的登录页面
	 * 
	 */
	@Override
	public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
		HttpServletRequest httpRequest = (HttpServletRequest) request;
		HttpServletResponse httpResponse = (HttpServletResponse)response;
		httpResponse.setCharacterEncoding("UTF-8");
		
		//加入跨域
		//部署建议使用nginx进行代理,理论上来说,不存在跨域的模式,如果需要跨域,可以打开注释
		/*httpResponse.setHeader("Access-Control-Allow-Origin", "*");
		httpResponse.setHeader("Access-Control-Allow-Methods", "*");
		httpResponse.setHeader("Access-Control-Max-Age", "100");
		httpResponse.setHeader("Access-Control-Allow-Headers", "content-type,x-requested-with");
		httpResponse.setHeader("Access-Control-Allow-Credentials", "false");*/
		
		String url = WebCommonUtils.findAccessPath(httpRequest);
		
		//http线程存在重用,在入口需要对ThreadLocal的登录数据进行清除
		SSOClientUtils.getInstance().clear();
		
		if(staticResource(url)) {
			chain.doFilter(httpRequest, response);
			return;
		}
		
		if(url.matches(ignorRegx)){
			//如果输入的url,可以匹配到忽略的正则表达式,则跳过当前的url访问
			chain.doFilter(httpRequest, response);
			return;
		}
		
		logger.info("SSOFilter handle the request:{}",url);
		String token = WebCommonUtils.findToken(httpRequest , tokenName);
		if(StringUtils.isNotBlank(token)){
			logger.info("find the token [{}] in the request" , token);
			//如果当前的请求能获取token,则进入token的有效性验证
			SSOUser ssoUser = tokenService.verify(token);
			if(ssoUser != null){
				logger.info("token verify result:{}" , ToStringBuilder.reflectionToString(ssoUser));
				//如果ssoUser不是null,认为token有效
				//XXX 这边不需要设置登录信息,如果使用threadlocal变量是需要设置的,如果使用singleton单例,可以不用设置,直接从缓存读取
				SSOClientUtils.getInstance().setCurrent(token);
				
				if(callback != null) {
					//如果已经配置loginCallback,会在登录以后,进行回调
					callback.putSSOUser(ssoUser);
				}
				
				chain.doFilter(httpRequest, response);
				return;
			}
//			WebCommonUtils.removeCookie(httpResponse, tokenName);
		}
		logger.info("the [{}] can't the token in the request" , url);
		//如果token不存在,判断是否存在ticket
		/*String ticket = findTicket(httpRequest);
		if(StringUtils.isNotBlank(ticket)){
			logger.info("find the ticket [{}] in the request" , ticket);
			//远程认证ticket,使用ticket登录的,一定是跨域的SSO,需要使用远程访问,请求ticket
			//只有这一个远程请求,是SSO客户端唯一请求SSO服务端,需要在SSO的服务端,记录当前的ip,方便登出的时候回调
			SSOUser ssoUser = ticketService.verifyTicket(ticket);
			logger.info("ticket verify result:{}" , ToStringBuilder.reflectionToString(ssoUser));
			if(ssoUser != null){
				//如果ssoUser不是null,认为token有效
				//XXX 这边不需要设置登录信息,如果使用threadlocal变量是需要设置的,如果使用singleton单例,可以不用设置,直接从缓存读取
				chain.doFilter(httpRequest, response);
				return;
			}
		}*/
		//下面的代码,就是代表无法获取登录标识
		//需要生成fromurl,跳转到sso的登录页面
		if(StringUtils.isNotBlank(afterLoginUrl)) {
			loginUrl = WebCommonUtils.generateFromUrl(loginUrl, "fromUrl", afterLoginUrl);
		}
		logger.info("current request will be interapted , the direct url :{}" , loginUrl);
		//判断当前的请求是不是ajax请求
		boolean isAjax = WebCommonUtils.isAjax(httpRequest);
		if(isAjax){
			//如果是ajax,返回401,由前端负责跳转到登录页面
			httpResponse.setStatus(HttpStatus.UNAUTHORIZED.value());
			Map<String , Object> resultMap = generateLoginMap(WebCommonUtils.generateTargetUrl(httpRequest.getContextPath(), loginUrl));
//			httpResponse.setContentType(MediaType.APPLICATION_JSON_VALUE);
			httpResponse.setContentType("application/json");
			JsonSerializer serializer = JsonSerializerFactory.findSerializer();
			httpResponse.getWriter().println(serializer.serialize(resultMap));
//			httpResponse.getWriter().println(JSON.toJSONString(resultMap));
			httpResponse.getWriter().flush();
			return;
		}
		
		httpResponse.sendRedirect(WebCommonUtils.generateTargetUrl(httpRequest.getContextPath(), loginUrl));
	}
	
	/**ajax请求需要生成跳转地址
	 * 要么在前端写死
	 * @return
	 */
	private Map<String , Object> generateLoginMap(String loginUrl){
		Map<String , Object> result = new HashMap<String , Object>();
		result.put("url", loginUrl);
		return result;
	}
	
	/**静态资源判断
	 * 过滤器开启所有的拦截,如果url中没有后缀或者后缀是.do,则需要经过过滤器
	 * 如果存在后缀,并且后缀不是.do,默认请求的是资源,不进过滤器
	 * 
	 * @param url
	 * @return
	 */
	private boolean staticResource(String url) {
		String prefix = WebCommonUtils.findUrlPrefix(url);
		if(StringUtils.isNotBlank(prefix)) {
			if(StringUtils.equalsIgnoreCase(prefix, ".do")) {
				//jinru
				return false;
			}else {
				//bu jinru
				return true;
			}
		}else {
			//jinru
			return false;
		}
		
	}
	
	@Override
	public void destroy() {

	}

}
